iT邦幫忙

2025 iThome 鐵人賽

DAY 16
0

今天本來要說極限梯度提升數 (XGBoost),但是我發現後面的篇幅可能快不夠了,今天開始的內容會調整成,無監督式學習 → 深度學習 → 如果有時間再回來補充 One-Class SVM 跟 XGBoost。

K-Means 是一種無監督學習中的聚類演算法,旨在將資料分為 K 個群集,使同一群集內的資料點之間相似度最高,而不同群集之間相似度最低。到這邊可能會有疑問,分類跟聚類差在哪? 分類要有標籤 (監督式學習),而聚類不需要有標籤 (無監督式學習),可以想像一下原始資料,如果要訓練分類模型,你在訓練之前就會知道每筆資料要分成什麼類別,但是到了聚類,你的資料完全分不出來該筆資料要分成什麼類別,只知道我這組資料要分成幾群。

模型介紹

模型邏輯與核心概念

  • 依照 K 的數量,隨機選出 K 個資料點作為初始質心 (centroids)
  • 每筆資料進行窮舉比較與 centroids 的歐幾里得距離,與距離最小的 centroids 為一群
  • 計算每個群集的平均值,為該群集所有資料點的 centroids
  • 重複指派與更新步驟,直到 centroids 不再顯著變動或達到最大迭代次數

Cost Function

$$
J = \sum_{i=1}^K \sum_{x \in C_i} |x - \mu_i|^2
$$

肘部法

  • K 值得決定,可以依照肘部法進行判斷

K-Means 的核心弱點

  • 初始中心點敏感問題 (Initialization Sensitivity): 當第一次初始化結果不好,導致誤差就很大,且沒辦法降低怎麼辦,即使資料本身具有明確的群聚特徵,也可能被錯誤分群,可以透過 K-Means++ 解決
    • 替代隨機初始化,透過機率機制選出「較分散」的中心點
    • 能顯著降低收斂到壞解的風險,推薦預設使用
    • sklearn 中內建支援: KMeans(init='k-means++') (預設就是這個)

優缺點

優點 缺點
計算快速、可擴展、易平行 需先給定 k,對初始化與尺度敏感
易實作、常作為分群基線 假設球形/大小相近群,對離群值非常敏感
可搭配 MiniBatchKMeans 處理大規模資料 對非凸形狀或重疊群表現不佳

模型實作

# Day 16 - K-Means on Iris (library dataset, full pipeline)
import seaborn as sns
import pandas as pd
import numpy as np

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans, MiniBatchKMeans
from sklearn.metrics import silhouette_score, adjusted_rand_score
from sklearn.model_selection import ParameterGrid
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

# 1) 載入資料(無需外部檔案)
iris = sns.load_dataset("iris")  # columns: sepal_length, sepal_width, petal_length, petal_width, species
X = iris.drop(columns=["species"])
y_true = iris["species"]  # 僅作對照評估,不參與訓練(非監督)

# 2) 工程化 Pipeline:標準化 + KMeans
def make_kmeans_pipe(k):
    return Pipeline([
        ("scaler", StandardScaler()),
        ("kmeans", KMeans(
            n_clusters=k,
            init="k-means++",
            n_init=10,            # 提升穩定性
            max_iter=300,
            random_state=42
        ))
    ])

# 3) 用 Elbow 與 Silhouette 輔助挑 k
K_RANGE = range(2, 8)  # Iris 通常 2~6 即可觀察
wcss, sils = [], []
for k in K_RANGE:
    pipe = make_kmeans_pipe(k).fit(X)
    labels = pipe.named_steps["kmeans"].labels_
    wcss.append(pipe.named_steps["kmeans"].inertia_)  # WCSS
    sils.append(silhouette_score(pipe.named_steps["scaler"].transform(X), labels))

print("k vs WCSS:", list(zip(K_RANGE, [round(v,2) for v in wcss])))
print("k vs Silhouette:", list(zip(K_RANGE, [round(v,4) for v in sils])))

# 4) 以 k=3 做主實驗(Iris 有 3 個物種)
k = 3
pipe = make_kmeans_pipe(k)
pipe.fit(X)
labels = pipe.named_steps["kmeans"].labels_

# 5) 非監督情境下的評估指標
sil = silhouette_score(pipe.named_steps["scaler"].transform(X), labels)
# 若有真實標籤可作參考(僅作對照用):Adjusted Rand Index(ARI)
ari = adjusted_rand_score(y_true, labels)
print(f"Silhouette Score (k={k}): {sil:.4f}")
print(f"Adjusted Rand Index vs species (k={k}): {ari:.4f}")

# 6) 簡易視覺化(PCA 2D)
Z = PCA(n_components=2, random_state=42).fit_transform(pipe.named_steps["scaler"].transform(X))
plt.figure(figsize=(6,5))
plt.scatter(Z[:,0], Z[:,1], c=labels, s=30)
plt.title(f"K-Means (k={k}) on Iris (PCA 2D)\nSilhouette={sil:.3f}, ARI={ari:.3f}")
plt.xlabel("PC1"); plt.ylabel("PC2")
plt.tight_layout(); plt.show()

# 7) 進一步:MiniBatchKMeans(大資料時)
mbk_pipe = Pipeline([
    ("scaler", StandardScaler()),
    ("kmeans", MiniBatchKMeans(
        n_clusters=k, init="k-means++", random_state=42, batch_size=64, n_init=10
    ))
]).fit(X)
mbk_labels = mbk_pipe.named_steps["kmeans"].labels_
mbk_sil = silhouette_score(mbk_pipe.named_steps["scaler"].transform(X), mbk_labels)
print(f"MiniBatchKMeans Silhouette (k={k}): {mbk_sil:.4f}")

# 8) 粗略參數掃描(僅示例)
grid = {"kmeans__n_clusters": [2,3,4], "kmeans__n_init": [10,20]}
best = None
for params in ParameterGrid(grid):
    candidate = Pipeline([("scaler", StandardScaler()), ("kmeans", KMeans(init="k-means++", max_iter=300, random_state=42))])
    candidate.set_params(**params).fit(X)
    labels_c = candidate.named_steps["kmeans"].labels_
    sil_c = silhouette_score(candidate.named_steps["scaler"].transform(X), labels_c)
    if (best is None) or (sil_c > best[0]):
        best = (sil_c, params)
print("Best by Silhouette:", round(best[0],4), best[1])

執行結果

k vs WCSS: [(2, 222.36), (3, 139.82), (4, 114.09), (5, 90.93), (6, 81.54), (7, 72.63)]
k vs Silhouette: [(2, 0.5818), (3, 0.4599), (4, 0.3869), (5, 0.3459), (6, 0.3171), (7, 0.3202)]
Silhouette Score (k=3): 0.4599
Adjusted Rand Index vs species (k=3): 0.6201
MiniBatchKMeans Silhouette (k=3): 0.4557
Best by Silhouette: 0.5818 {'kmeans__n_clusters': 2, 'kmeans__n_init

結果評估

  • k 值與 WCSS (Elbow 法)
    • WCSS 從 k=2 到 k=7 持續下降,且在 k=3 後下降幅度明顯趨緩 (139.82 → 114.09 → 90.93)
    • 這符合 Elbow 法的典型訊號:在 k=3 之後邊際效益降低
  • k 值與 Silhouette Score
    • 最高分在 k=2 (0.5818),k=3 則下降至 0.4599,之後隨著 k 增加分數持續降低
    • 這意味著,若純粹以內部結構 (凝聚度與分離度) 來衡量,k=2 是最緊密且分離度最佳的分群方案
    • 但 Silhouette 只考慮幾何結構,不代表與實際業務需求或真實標籤完全對應
  • k=3 的分群表現
    • Silhouette Score = 0.4599,屬於中等偏弱的群分離度,叢集邊界可能有交疊
    • Adjusted Rand Index (ARI) = 0.6201:與真實 species 標籤有中等對齊程度,表示模型能部分復原真實物種結構,但錯分率仍顯著
    • 與 MiniBatchKMeans 相比,Silhouette 由 0.4599 降至 0.4557,效能損失不大,但 MiniBatch 在大資料場景下能顯著節省計算時間
  • 參數搜尋結果
    • 最佳 Silhouette 方案是 k=2, n_init=10 (0.5818)
    • 這與初步觀察一致,顯示在內部幾何結構下,Iris 資料更適合分成兩群,但這不符合三物種的領域先驗

結語

K-Means 在本篇範例中清楚展現了它作為無監督式分群基線的特性: 計算效率高、實作簡單、可快速提供初步的資料結構洞察。透過 Elbow 法與 Silhouette Score 的雙重分析,我們觀察到在 Iris 資料集上,幾何結構最佳的分群數為 k=2,而符合領域知識的 k=3 則在分群品質上略顯不足,顯示資料真實分佈與理想分群假設之間的落差。這種差異提醒我們,分群結果不能僅憑數學指標決定,還需結合業務目標與領域先驗做取捨。

總結來說,K-Means 是值得在專案初期引入的分群起點模型,但應搭配多種指標與方法驗證,並在必要時替換為更契合資料幾何與業務需求的分群策略。


上一篇
(Day 15) 隨機森林 (Random Forest)
下一篇
(Day 17) 淺談深度學習 (Deep Learning)
系列文
30 天入門常見的機器學習演算法30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言